/*
 * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.spi.servicecontext;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.*;

import org.omg.CORBA.OctetSeqHelper;
import org.omg.CORBA.SystemException;
import org.omg.CORBA.INTERNAL;
import org.omg.CORBA.CompletionStatus;
import org.omg.CORBA_2_3.portable.OutputStream;
import org.omg.CORBA_2_3.portable.InputStream;

import com.sun.org.omg.SendingContext.CodeBase;

import com.sun.corba.se.spi.ior.iiop.GIOPVersion;

import com.sun.corba.se.spi.orb.ORB;

import com.sun.corba.se.spi.logging.CORBALogDomains;


import com.sun.corba.se.spi.servicecontext.ServiceContext;
import com.sun.corba.se.spi.servicecontext.ServiceContextRegistry;
import com.sun.corba.se.spi.servicecontext.ServiceContextData;
import com.sun.corba.se.spi.servicecontext.UnknownServiceContext;

import com.sun.corba.se.impl.encoding.CDRInputStream;
import com.sun.corba.se.impl.encoding.EncapsInputStream;
import com.sun.corba.se.impl.orbutil.ORBUtility;
import com.sun.corba.se.impl.util.Utility;
import com.sun.corba.se.impl.logging.ORBUtilSystemException;

import sun.corba.EncapsInputStreamFactory;


public class ServiceContexts {

  private static boolean isDebugging(OutputStream os) {
    ORB orb = (ORB) (os.orb());
    if (orb == null) {
      return false;
    }
    return orb.serviceContextDebugFlag;
  }

  private static boolean isDebugging(InputStream is) {
    ORB orb = (ORB) (is.orb());
    if (orb == null) {
      return false;
    }
    return orb.serviceContextDebugFlag;
  }

  private void dprint(String msg) {
    ORBUtility.dprint(this, msg);
  }

  public static void writeNullServiceContext(OutputStream os) {
    if (isDebugging(os)) {
      ORBUtility.dprint("ServiceContexts", "Writing null service context");
    }
    os.write_long(0);
  }

  /**
   * Given the input stream, this fills our service
   * context map.  See the definition of scMap for
   * details.  Creates a HashMap.
   *
   * Note that we don't actually unmarshal the
   * bytes of the service contexts here.  That is
   * done when they are actually requested via
   * get(int).
   */
  private void createMapFromInputStream(InputStream is) {
    orb = (ORB) (is.orb());
    if (orb.serviceContextDebugFlag) {
      dprint("Constructing ServiceContexts from input stream");
    }

    int numValid = is.read_long();

    if (orb.serviceContextDebugFlag) {
      dprint("Number of service contexts = " + numValid);
    }

    for (int ctr = 0; ctr < numValid; ctr++) {
      int scId = is.read_long();

      if (orb.serviceContextDebugFlag) {
        dprint("Reading service context id " + scId);
      }

      byte[] data = OctetSeqHelper.read(is);

      if (orb.serviceContextDebugFlag) {
        dprint("Service context" + scId + " length: " + data.length);
      }

      scMap.put(new Integer(scId), data);
    }
  }

  public ServiceContexts(ORB orb) {
    this.orb = orb;
    wrapper = ORBUtilSystemException.get(orb,
        CORBALogDomains.RPC_PROTOCOL);

    addAlignmentOnWrite = false;

    scMap = new HashMap();

    // Use the GIOP version of the ORB.  Should
    // be specified in ServiceContext.
    // See REVISIT below concerning giopVersion.
    giopVersion = orb.getORBData().getGIOPVersion();
    codeBase = null;
  }

  /**
   * Read the Service contexts from the input stream.
   */
  public ServiceContexts(InputStream s) {
    this((ORB) (s.orb()));

    // We need to store this so that we can have access
    // to the CodeBase for unmarshaling possible
    // RMI-IIOP valuetype data within an encapsulation.
    // (Known case: UnknownExceptionInfo)
    codeBase = ((CDRInputStream) s).getCodeBase();

    createMapFromInputStream(s);

    // Fix for bug 4904723
    giopVersion = ((CDRInputStream) s).getGIOPVersion();
  }

  /**
   * Find the ServiceContextData for a given scId and unmarshal
   * the bytes.
   */
  private ServiceContext unmarshal(Integer scId, byte[] data) {

    ServiceContextRegistry scr = orb.getServiceContextRegistry();

    ServiceContextData scd = scr.findServiceContextData(scId.intValue());
    ServiceContext sc = null;

    if (scd == null) {
      if (orb.serviceContextDebugFlag) {
        dprint("Could not find ServiceContextData for "
            + scId
            + " using UnknownServiceContext");
      }

      sc = new UnknownServiceContext(scId.intValue(), data);

    } else {

      if (orb.serviceContextDebugFlag) {
        dprint("Found " + scd);
      }

      // REVISIT.  GIOP version should be specified as
      // part of a service context's definition, so should
      // be accessible from ServiceContextData via
      // its ServiceContext implementation class.
      //
      // Since we don't have that, yet, I'm using the GIOP
      // version of the input stream, presuming that someone
      // can't send a service context of a later GIOP
      // version than its stream version.
      //
      // Note:  As of Jan 2001, no standard OMG or Sun service contexts
      // ship wchar data or are defined as using anything but GIOP 1.0 CDR.
      EncapsInputStream eis
          = EncapsInputStreamFactory.newEncapsInputStream(orb,
          data,
          data.length,
          giopVersion,
          codeBase);
      eis.consumeEndian();

      // Now the input stream passed to a ServiceContext
      // constructor is already the encapsulation input
      // stream with the endianness read off, so the
      // service context should just unmarshal its own
      // data.
      sc = scd.makeServiceContext(eis, giopVersion);
      if (sc == null) {
        throw wrapper.svcctxUnmarshalError(
            CompletionStatus.COMPLETED_MAYBE);
      }
    }

    return sc;
  }

  public void addAlignmentPadding() {
    // Make service context 12 bytes longer by adding
    // JAVAIDL_ALIGN_SERVICE_ID service context at end.
    // The exact length
    // must be >8 (minimum service context size) and
    // =4 mod 8, so 12 is the minimum.
    addAlignmentOnWrite = true;
  }

  /**
   * Hopefully unused scid:  This should be changed to a proper
   * VMCID aligned value.  REVISIT!
   */
  private static final int JAVAIDL_ALIGN_SERVICE_ID = 0xbe1345cd;

  /**
   * Write the service contexts to the output stream.
   *
   * If they haven't been unmarshaled, we don't have to
   * unmarshal them.
   */
  public void write(OutputStream os, GIOPVersion gv) {
    if (isDebugging(os)) {
      dprint("Writing service contexts to output stream");
      Utility.printStackTrace();
    }

    int numsc = scMap.size();

    if (addAlignmentOnWrite) {
      if (isDebugging(os)) {
        dprint("Adding alignment padding");
      }

      numsc++;
    }

    if (isDebugging(os)) {
      dprint("Service context has " + numsc + " components");
    }

    os.write_long(numsc);

    writeServiceContextsInOrder(os, gv);

    if (addAlignmentOnWrite) {
      if (isDebugging(os)) {
        dprint("Writing alignment padding");
      }

      os.write_long(JAVAIDL_ALIGN_SERVICE_ID);
      os.write_long(4);
      os.write_octet((byte) 0);
      os.write_octet((byte) 0);
      os.write_octet((byte) 0);
      os.write_octet((byte) 0);
    }

    if (isDebugging(os)) {
      dprint("Service context writing complete");
    }
  }

  /**
   * Write the service contexts in scMap in a desired order.
   * Right now, the only special case we have is UnknownExceptionInfo,
   * so I'm merely writing it last if present.
   */
  private void writeServiceContextsInOrder(OutputStream os, GIOPVersion gv) {

    // Temporarily remove this rather than check it per iteration
    Integer ueInfoId
        = new Integer(UEInfoServiceContext.SERVICE_CONTEXT_ID);

    Object unknownExceptionInfo = scMap.remove(ueInfoId);

    Iterator iter = scMap.keySet().iterator();

    while (iter.hasNext()) {
      Integer id = (Integer) iter.next();

      writeMapEntry(os, id, scMap.get(id), gv);
    }

    // Write the UnknownExceptionInfo service context last
    // (so it will be after the CodeBase) and restore it in
    // the map.
    if (unknownExceptionInfo != null) {
      writeMapEntry(os, ueInfoId, unknownExceptionInfo, gv);

      scMap.put(ueInfoId, unknownExceptionInfo);
    }
  }

  /**
   * Write the given entry from the scMap to the OutputStream.
   * See note on giopVersion.  The service context should
   * know the GIOP version it is meant for.
   */
  private void writeMapEntry(OutputStream os, Integer id, Object scObj, GIOPVersion gv) {

    // If it's still in byte[] form, we don't need to
    // unmarshal it here, just copy the bytes into
    // the new stream.

    if (scObj instanceof byte[]) {
      if (isDebugging(os)) {
        dprint("Writing service context bytes for id " + id);
      }

      OctetSeqHelper.write(os, (byte[]) scObj);

    } else {

      // We actually unmarshaled it into a ServiceContext
      // at some point.
      ServiceContext sc = (ServiceContext) scObj;

      if (isDebugging(os)) {
        dprint("Writing service context " + sc);
      }

      sc.write(os, gv);
    }
  }

  /**
   * Add a service context to the stream, if there is not already
   * a service context in this object with the same id as sc.
   */
  public void put(ServiceContext sc) {
    Integer id = new Integer(sc.getId());
    scMap.put(id, sc);
  }

  public void delete(int scId) {
    this.delete(new Integer(scId));
  }

  public void delete(Integer id) {
    scMap.remove(id);
  }

  public ServiceContext get(int scId) {
    return this.get(new Integer(scId));
  }

  public ServiceContext get(Integer id) {
    Object result = scMap.get(id);
    if (result == null) {
      return null;
    }

    // Lazy unmarshaling on first use.
    if (result instanceof byte[]) {

      ServiceContext sc = unmarshal(id, (byte[]) result);

      scMap.put(id, sc);

      return sc;
    } else {
      return (ServiceContext) result;
    }
  }

  private ORB orb;

  /**
   * Map of all ServiceContext objects in this container.
   *
   * Keys are java.lang.Integers for service context IDs.
   * Values are either instances of ServiceContext or the
   * unmarshaled byte arrays (unmarshaled on first use).
   *
   * This provides a mild optimization if we don't happen to
   * use a given service context, but it's main advantage is
   * that it allows us to change the order in which we
   * unmarshal them.  We need to do the UnknownExceptionInfo service
   * context after the SendingContextRunTime service context so that we can
   * get the CodeBase if necessary.
   */
  private Map scMap;

  /**
   * If true, write out a special alignment service context to force the
   * correct alignment on re-marshalling.
   */
  private boolean addAlignmentOnWrite;

  private CodeBase codeBase;
  private GIOPVersion giopVersion;
  private ORBUtilSystemException wrapper;
}
